using System;
using System.Collections.Generic;
using TALoaders;
using System.Diagnostics;

namespace TAAlert.BackTest
{
    class BackTestManager
    {
        static private readonly TraceSwitch traceM = new TraceSwitch("BackTestManager", "Debug messages from BackTestManager");
        
        private SortedList<DateTime, double> closePriceM;   // Used for performance calculations
        private SortedList<DateTime, double> signalPricesM; // Used for strategy 
        private DateTime startDateM;
        private DateTime endDateM;
        private double transactionCostM;
        private bool calcYearlyPerfM;   // Should yearly performance figures be calculated?
        private bool calcMonthlyPerfM;  // SHould monthly performance figures be calculated?

        /// <summary>Constructor. Store all common parameters</summary>
        /// <param name="signalQuoteType">What quote (e.g. close or (H+L)/2) should be used ot generate signals</param>
        public BackTestManager(string ticker, Loader loader, DateTime startDate, DateTime endDate, double transactionCost, QuoteType signalQuoteType,
            bool calcYearlyPerf, bool calcMonthlyPerf)
        {
            Debug.WriteLineIf(traceM.TraceInfo, "--> BackTestManager(" + ticker + ", " + loader.ToString() + ", " + startDate.ToShortDateString() + ", " + endDate.ToShortDateString() + ", TC=" + transactionCost + ")");
            // Load Quotes
            BaseLoader downloader = DownloaderFactory.createCachedDownloader(loader.ToString());
            DateTime calcStartDate = startDate.AddYears(-1);    // we need to warm-start calculations well before the back-testing start.
            SortedList<DateTime, Quote> quotes = downloader.getHistoricalPrices(ticker, calcStartDate, endDate);
            // Set-up other private memebers
            this.closePriceM = Utilities.getTimeSeries(quotes, QuoteType.AdjClose);
            this.signalPricesM = Utilities.getTimeSeries(quotes, signalQuoteType);
            this.startDateM = startDate;
            this.endDateM = endDate;
            this.transactionCostM = transactionCost;
            this.calcYearlyPerfM = calcYearlyPerf;
            this.calcMonthlyPerfM = calcMonthlyPerf;
        }

        /// <summary>Wrapper for total and in-period Performance statistics</summary>
        public struct PerfSummary
        {
            private PerfStatistics totalM;
            private List<PerfStatistics> monthlyM;
            private List<PerfStatistics> yearlyM;

            /// <summary>Set up the total and monthly perofrmance statistics</summary>
            /// <param name="total">PerfStatistics for total back-testing period</param>
            /// <param name="yearly">List of PerStatistics for yeach year in the total perios</param>
            /// <param name="monthly">List of PerStatistics for each month in the total period</param>
            public PerfSummary(PerfStatistics total, List<PerfStatistics> yearly, List<PerfStatistics> monthly)
            {
                this.totalM = total;
                this.yearlyM = yearly;
                this.monthlyM = monthly;
            }

            /// <summary> Return performance statistics for total period</summary>
            public PerfStatistics Total
            {
                get { return this.totalM; }
            }
            /// <summary>Return performance statistics for yeach year in the total period</summary>
            public List<PerfStatistics> Yearly
            {
                get { return this.yearlyM; }
            }
            /// <summary>Return list of performance statistics for each month in the total period</summary>
            public List<PerfStatistics> Monthly
            {
                get { return this.monthlyM; }
            }
        }

        /// <summary>Perform back-testing of Single EMA strategy.</summary>
        /// <param name="nMIN">the starting period (inclusive)</param>
        /// <param name="nMAX">the ending period (inclusive)</param>
        /// <returns>Mapping between nEMA and PerfSummary: Total and montly performance statistics</returns>
        /// <remarks>Add also LAG</remarks>
        public SortedList<int, PerfSummary> backTestEMAStrategy(int nMIN, int nMAX, int step)
        {
            Debug.WriteLineIf(traceM.TraceInfo, "--> BackTestManager.backTestEMAStrategy(nMIN=" + nMIN + ", nMAX=" + nMAX + ", step=" + step + ")");
            Debug.Indent();

            if (nMIN > nMAX || step < 1 )
                throw new Exception("Inconsistent input parameters for TwoEMA Back-testing");
            int maxNOFScenarios = (nMAX - nMIN + 1) / step;
            SortedList<int, PerfSummary> result = new SortedList<int, PerfSummary>(nMAX - nMIN + 1);
            for (int nEMA = nMIN; nEMA <= nMAX; nEMA = nEMA + step)
            {
                EMAStrategy emaStrategy = new EMAStrategy(nEMA, this.startDateM, this.endDateM, this.signalPricesM);
                PerfSummary perf = this.calculateStatistics(emaStrategy);
                result.Add(nEMA, perf);
            }

            Debug.Unindent();
            Debug.WriteLineIf(traceM.TraceInfo, "...Finished. Back-tested " + result.Count + " scenarios");
            return result;
        }

        /// <summary> Wrapper for TwoEma back-testing results: (nShort, nLong, PerfSummary)</summary>
        public struct TwoEmaPerfSummary
        {
            private int nEmaShortM;         // short ema period
            private int nEmaLongM;          // long ema period
            private double volTreasholdM;   // cut-out volatility treashold
            private PerfSummary summaryM;
            public TwoEmaPerfSummary(int nEmaShort, int nEmaLong, double volTreashold, PerfSummary summary)
            {
                this.nEmaShortM = nEmaShort;
                this.nEmaLongM = nEmaLong;
                this.summaryM = summary;
                this.volTreasholdM = volTreashold;
            }

            public int NEmaShort
            {
                get { return this.nEmaShortM; }
            }
            public int NEmaLong
            {
                get { return this.nEmaLongM; }
            }
            public double VolatilityTreashold
            {
                get { return this.volTreasholdM; }
            }
            public PerfSummary Summary
            {
                get { return this.summaryM; }
            }
        }

        /// <summary>Perform back-testing of TwoEMA strategy</summary>
        /// <param name="nShortMIN">the MIN shorter EMA period (inclusive)</param>
        /// <param name="nShortMAX">the MAX shorter EMA period (inclusive)</param>
        /// <param name="nLongMIN">the MIN longer EMA period (inclusive)</param>
        /// <param name="nLongMAX">the MAX shorter EMA period (inclusive)</param>
        /// <returns>List of scenarios (nShort, nLong, PerfSummary)</returns>
        /// <remarks>Only nShort less than nLong back tested</remarks>
        public List<TwoEmaPerfSummary> backTestTwoEMAStrategy(int nShortMIN, int nShortMAX, int shortSTEP, 
                                                                int nLongMIN, int nLongMAX, int longSTEP,
                                                                double volTMIN, double volTMAX, double volStep)
        {
            Debug.WriteLineIf(traceM.TraceInfo, "--> BackTestManager.backTestTwoEMAStrategy(nShortMIN=" + nShortMIN + ", nShortMAX=" + nShortMAX + ", nLongMIN=" + nLongMIN + ", nLongMAX=" + nLongMAX + ")");
            Debug.Indent();

            if (nShortMIN > nShortMAX || nLongMIN > nLongMAX || shortSTEP < 1 || longSTEP < 1)
                throw new Exception("Inconsistent input parameters for TwoEMA Back-testing");

            int maxNOfScenarios = (nLongMAX - nLongMIN + 1) * (nShortMAX - nShortMIN + 1) / (shortSTEP * longSTEP);
            List<TwoEmaPerfSummary> result = new List<TwoEmaPerfSummary>(maxNOfScenarios);
            for (int nLong = nLongMIN; nLong <= nLongMAX; nLong = nLong + longSTEP)
                for (int nShort = nShortMIN; nShort <= nShortMAX && nShort < nLong; nShort = nShort + shortSTEP)
                    for (double volT = volTMIN; volT <= volTMAX; volT += volStep)
                {
                    Strategy twoEmaStrategy = new TwoEMAStrategy(nShort, nLong, volT, this.startDateM, this.endDateM, this.signalPricesM);
                    PerfSummary pSummary = this.calculateStatistics(twoEmaStrategy);
                    TwoEmaPerfSummary twoPertSummary = new TwoEmaPerfSummary(nShort, nLong, volT, pSummary);
                    result.Add(twoPertSummary);
                }

            Debug.Unindent();
            Debug.WriteLineIf(traceM.TraceInfo, "...Finished. Back-tested " + result.Count + " scenarios");
            return result;
        }

        /// <summary>Wrapper for MACD Back-test result: (nShort, nLong, nFast, PerfSummary)</summary>
        public struct MACDPerfSummary
        {
            private int nEmaShortM;
            private int nEmaLongM;
            private int nFastM;
            private PerfSummary summaryM;
            public MACDPerfSummary(int nEmaShort, int nEmaLong, int nFast, PerfSummary summary)
            {
                this.nEmaShortM = nEmaShort;
                this.nEmaLongM = nEmaLong;
                this.nFastM = nFast;
                this.summaryM = summary;
            }
            public int NEmaShort
            {
                get { return this.nEmaShortM; }
            }
            public int NEmaLong
            {
                get { return this.nEmaLongM; }
            }
            public int NFast
            {
                get { return this.nFastM; }
            }
            public PerfSummary Summary
            {
                get { return this.summaryM; }
            }
        }

        /// <summary>Performance back-testing of MACD strategy</summary>
        /// <param name="nShortMIN">MIN value of EMA period nShort</param>
        /// <param name="nShortMAX">MAX value of EMA period nShort</param>
        /// <param name="nShortStep">STEP for EMA period nShort</param>
        /// <param name="nLongMIN">MIN value of EMA period nLong</param>
        /// <param name="nLongMAX">MAX value of EMA period nLong</param>
        /// <param name="nLongStep">STEP for EMA period nLong</param>
        /// <param name="nFastMIN">MIN value of EMA period nFast</param>
        /// <param name="nFastMAX">MAX value of EMA period nFast</param>
        /// <param name="nFastStep">STEP for EMA period nFast</param>
        /// <returns>List of scenarios (nShort, nLong, nFast, PerfSummary)</returns>
        public List<MACDPerfSummary> backTestMACDStrategy(int nShortMIN, int nShortMAX, int shortStep,
                                                            int nLongMIN, int nLongMAX, int longStep,
                                                            int nFastMIN, int nFastMAX, int fastStep)
        {
            Debug.WriteLineIf(traceM.TraceInfo, "--> BackTestManager.backTestMACDStrategy(nShortMIN=" + nShortMIN + ", nShortMAX=" + nShortMAX + ", nShortStep=" + shortStep +
                ", nLongMIN=" + nLongMIN + ", nLongMAX=" + nLongMAX + ", nLongStep=" + longStep + ", nFastMIN=" + nFastMIN + ", nFastMAX=" + nFastMAX + ", nFastStep=" + fastStep + ")");
            Debug.Indent();

            if (nShortMIN > nShortMAX || nLongMIN > nLongMAX || nFastMIN > nFastMAX || shortStep < 1 || longStep < 1 || fastStep < 1)
                throw new Exception("Inconsistent input parameters for MACD Back-testing");

            int maxNOfScenarios = (nShortMAX - nShortMIN + 1) * (nLongMAX - nLongMIN + 1) * (nFastMAX - nFastMIN + 1) / (shortStep * longStep * fastStep);
            List<MACDPerfSummary> result = new List<MACDPerfSummary>(maxNOfScenarios);
            for (int nLong = nLongMIN; nLong <= nLongMAX; nLong = nLong + longStep)
                for (int nShort = nShortMIN; nShort <= nShortMAX && nShort < nLong; nShort = nShort + shortStep)
                    for (int nFast = nFastMIN; nFast <= nFastMAX; nFast = nFast + fastStep)
                    {
                        Strategy macdStrategy = new MACDStrategy(nShort, nLong, nFast, this.startDateM, this.endDateM, this.signalPricesM);
                        PerfSummary pSummary = this.calculateStatistics(macdStrategy);
                        MACDPerfSummary macdPerf = new MACDPerfSummary(nShort, nLong, nFast, pSummary);
                        result.Add(macdPerf);
                    }

            Debug.Unindent();
            Debug.WriteLineIf(traceM.TraceInfo, "...Finished. Back-tested " + result.Count + " scenarios");
            return result;
        }
        
        /// <summary>Wrapper for VEMA BAck-test result: (nEMA1, nEMA2, volTreashold, PerfSummary)</summary>
        public struct VEMAPerfSummary
        {
            private int nEma1M;     // EMA period for volatilities below volTreashold   (if 0 then neutral position)
            private int nEma2M;     // EMA period for volatilities above volTreashold   (if 0 then neutral position)
            private double volTreasholdM;
            private PerfSummary summaryM;
            public VEMAPerfSummary(int nEma1, int nEma2, double volTreashold, PerfSummary summary)
            {
                this.nEma1M = nEma1;
                this.nEma2M = nEma2;
                this.volTreasholdM = volTreashold;
                this.summaryM = summary;
            }
            public int NEma1
            {
                get { return this.nEma1M; }
            }
            public int NEma2
            {
                get { return this.nEma2M; }
            }
            public double VolTreashold
            {
                get { return this.volTreasholdM; }
            }
            public PerfSummary Summary
            {
                get { return this.summaryM; }
            }
        }

        /// <summary>Performance back-testing for VEMA strategy</summary>
        /// <param name="nEma1MIN">min value for the period nEMA1</param>
        /// <param name="nEma1MAX">max value for the period nEMA1</param>
        /// <param name="step1">step for the period nEMA1</param>
        /// <param name="nEma2MIN">min value for the period nEMA2</param>
        /// <param name="nEma2MAX">max value for the period nEMA2</param>
        /// <param name="step2">step for the period nEMA1</param>
        /// <param name="volTMIN">min value for volatility treashold</param>
        /// <param name="volTMAX">max value for colatility treashold</param>
        /// <param name="volStep">step for volatility treashold</param>
        /// <returns>List of back-test scenarios with results</returns>
        public List<VEMAPerfSummary> backTestVEMAStrategy(int nEma1MIN, int nEma1MAX, int step1,
                                                            int nEma2MIN, int nEma2MAX, int step2,
                                                            double volTMIN, double volTMAX, double volStep)
        {
            Debug.WriteLineIf(traceM.TraceInfo, "--> baskTestManager.backTestVEMAStrategy(nEMA1={" + nEma1MIN + ":" + nEma1MAX + ":" + step1 + "}, " +
                "nEMA2={" + nEma2MIN + ":" + nEma2MAX + ":" + step2 + "}, volT={" + volTMIN + ":" + volTMAX + ":" + volStep + "})");
            Debug.Indent();
            if (nEma1MIN > nEma1MAX || nEma2MIN > nEma2MAX || volTMIN > volTMAX || step1 < 1 || step2 < 1 || volStep <= 0)
                throw new Exception("Inconsistent input parameters for VEMA Back-testing");
            
            int maxNOfScenarios = (int)((nEma1MAX - nEma1MIN + 2) * (nEma2MAX - nEma2MIN + 1) * ( volTMAX - volTMIN ) / (step1 * step2 * volStep));
            List<VEMAPerfSummary> result = new List<VEMAPerfSummary>(maxNOfScenarios);
            int n2 = 0; // The first round is with neutral position for vol above treashold
            while (n2 <= nEma2MAX)
            {
                for (int n1 = nEma1MIN; n1 <= nEma1MAX; n1 += step1)
                    for (double volT = volTMIN; volT <= volTMAX; volT += volStep)
                    {
                        Strategy vemaStrategy = new VEMAStrategy(n1, n2, volT, this.startDateM, this.endDateM, this.signalPricesM);
                        PerfSummary summary = this.calculateStatistics(vemaStrategy);
                        VEMAPerfSummary vemaPerf = new VEMAPerfSummary(n1, n2, volT, summary);
                        result.Add(vemaPerf);
                    }

                if (n2 == 0)
                    n2 = nEma2MIN;
                else
                    n2 += step2;
            }
            Debug.Unindent();
            Debug.WriteLineIf(traceM.TraceInfo, "...Finished. Back-tested " + result.Count + " scenarios");
            return result;
        }

        /// <summary>Performance calculation inner loop. Given strategy object generate positions and calculate performance</summary>
        /// <returns>PerfSummary object</returns>
        private PerfSummary calculateStatistics(Strategy strategy)
        {
            // 1. Generate strategy positions
            SortedList<DateTime, int> positions = strategy.generate();
            // 2. Evaluate performance
            PerformanceCalculator perfCalc = new PerformanceCalculator(this.closePriceM, positions, this.transactionCostM);
            //  2.1 Total
            PerfStatistics total = perfCalc.calculateStatistics(this.startDateM, this.endDateM);
            //  2.2 Annual
            List<PerfStatistics> yearly = new List<PerfStatistics>();
            if (this.calcYearlyPerfM)
                yearly = perfCalc.calcIntraPeriodStatistics(this.startDateM, this.endDateM, PerformanceCalculator.Frequency.Yearly);
            //  2.3 Monthly
            List<PerfStatistics> monthly = new List<PerfStatistics>();
            if (this.calcMonthlyPerfM)
                monthly = perfCalc.calcIntraPeriodStatistics(this.startDateM, this.endDateM, PerformanceCalculator.Frequency.Monthly);
            // Create and return PerfSummary
            return new PerfSummary(total, yearly, monthly);
        }
    }
}
